<?php
// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2023-05-14 14:56:38
// |@----------------------------------------------------------------------
// |@LastEditTime : 2023-06-03 23:05:42
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : 
// |@----------------------------------------------------------------------
// |@FilePath     : Menu.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2023 http://www.ladmin.cn   All rights reserved. 
// ------------------------------------------------------------------------
namespace app\api\model;

use think\admin\Model;
use think\admin\extend\DataExtend;

class Menu extends Model
{
    /**
     * 获取用户菜单数据
     * @param int|null $userId 用户id
     * @param array $role_list 用户角色列表
     * @return \think\Collection|array
     */
    public static function getUserMenu(?int $userId, array $role_list): \think\Collection|array
    {
        if ($userId !== null && $userId !== 1) {
            // 非超级管理员用户需要根据角色和权限获取自己的菜单列表
            $auth = Auth::whereIn('role_id', $role_list)->find();
            if (!$auth) {
                // 如果没有对应的权限，则无法获取菜单
                return collect([]);
            }

            // 使用菜单 ID 列表来过滤菜单数据，并按照 sort 字段排序
            $menu_list = static::mk()->whereIn('id', json_decode($auth->menu_id_list))->order('sort')->select();
        } else {
            // 超级管理员可以获取所有菜单数据，并按照 sort 字段排序
            $menu_list = static::mk()->order('sort')->select();
        }

        // 格式化菜单列表，方便前端渲染菜单和路由
        $menus = self::formatMenuList($menu_list);
        // 获取菜单权限列表，方便后续接口权限控制
        $permissions = self::getMenuPermissions($menu_list);
        
        // 返回菜单列表数据
        return [
            'menu' => DataExtend::arr2tree(array_values($menus)),
            'permissions' => array_values($permissions['api_list']),
            'api_auth' => array_values($permissions['api_auth']),
        ];
    }

    /**
     * 格式化菜单列表，将数据转换为前端所需的格式
     * @param \think\Collection $menu_list 菜单列表数据
     * @return array
     */
    private static function formatMenuList(\think\Collection $menu_list): array
    {
        $menus = [];

        foreach ($menu_list as $v){
            // 筛选非按钮类型的菜单
            if ($v['type'] != 'button'){
                // 构造菜单的 meta 信息，包含标题、图标、类型、颜色、激活状态等
                $meta = [
                    'title' => $v['title'],
                    'icon' => $v['icon'],
                    'type' => $v['type'],
                    'color' => $v['color'],
                    'active' => $v['active'],
                    'hidden' => $v['hidden'] == 1 ? false : true,
                    'hidden_breadcrumb' => $v['hidden_breadcrumb'] == 1 ? false : true,
                ];

                // 构造菜单信息，包含菜单 ID、父级菜单 ID、名称、路径、meta 信息、组件地址等
                $menus[$v['id']] = [
                    'id' => $v['id'],
                    'pid' => $v['pid'],
                    'name' => $v['name'],
                    'path' => $v['path'],
                    'meta' => $meta,
                    'component' => $v['component'],
                ];
            }
        }

        return  DataExtend::arrfilterEmptyValues($menus);
    }

    /**
     * 获取菜单权限列表，方便后续接口权限控制
     * @param array $menu_list 格式化后的菜单列表数据
     * @return array
     */
    private static function getMenuPermissions(\think\Collection $menu_list): array
    {
        $permissions = [];

        foreach ($menu_list as $v){
            // 解析菜单的接口权限列表，加入权限数据中
            $v['api_list'] = !empty($v['api_list']) ? json_decode($v['api_list'], true) : [];
            foreach ($v['api_list'] as $vs){
                $permissions['api_list'][$vs['url']] = $vs['code'];
                $permissions['api_auth'][$vs['url']] = $vs;
            }
        }

        return $permissions;
    }
    
    /**
     * 获取菜单数据列表
     * @param  array $data 查询条件列表，包含 title 等字段
     * @return array       返回菜单数据列表
     */
    public static function listMenu(array $data): array
    {
        // 根据查询条件组织 where 子句查询数组。
        $where = !empty($data['title']) ? [["title", "like", "%" . $data['title'] . "%"]] : [["id", ">", 0]];
        // 查询数据库获取符合条件的菜单数据。
        $menus = static::mk()->where($where)->order('pid asc, sort asc')->select()->toArray();
        // 遍历菜单数据列表，解析 api_list 字段为数组格式，并将解析后的结果赋值给对应的菜单数据。
        foreach ($menus as $key => $menu) {
            !empty($menu['api_list']) ? $menus[$key]['api_list'] = json_decode($menu['api_list'], true) : $menus[$key]['api_list'] = $menu['api_list'];
        }
        // 返回菜单数据列表。
        return $menus ? $menus : [];
    }

    /**
     * 保存菜单数据
     * @param array $data 菜单数据列表，包含 title 等字段
     * @return array 返回保存后的菜单数据
     */
    public static function saveMenu(array $data): array
    {
        $fn = function () use ($data) {
            // 如果没有传入 id 值，则执行插入操作。
            if (empty($data['id'])) {
                $data['id'] = static::mk()->strict(false)->insertGetId($data);
            } else {
                // 如果有 id 值，则执行更新操作，并将 api_list 字段转成 JSON 格式存储。
                $data['api_list'] = json_encode($data['api_list']);
                static::mk()->strict(false)->where('id', $data['id'])->update($data);
            }

            // 返回保存后的菜单数据。
            return $data;
        };

        return static::mk()->runTransaction($fn);
    }

    /**
     * 对菜单项进行排序
     * @param array $data 包含 dragid, dropid 和 droptype 等字段的数组
     * @return array 返回排序后的菜单项
     */
    public static function sortMenu(array $data): array
    {
        $fn = function () use ($data) {
            // 查询拖动和放置的菜单项
            /** @var \think\admin\Model $dragItem */
            $dragItem = static::getMenuItemById($data['dragid']);
            /** @var \think\admin\Model $dropItem */
            $dropItem = static::getMenuItemById($data['dropid']);
            $pid = $dropItem->pid;

            // 确认拖动和放置的菜单项有效性
            if ($dragItem && $dropItem && $dragItem->id !== $dropItem->id) {
                // 处理不同的拖动类型（before/after/inner）
                switch ($data['droptype']) {
                    // 在放置项前插入拖动项
                    case 'before':
                        // 如果 pid 为 0，则无需对兄弟节点进行排序更新
                        $siblings = $pid !== 0 ? static::getSiblings($dragItem, $dropItem, $pid) : [];
                        // 更新拖动元素的位置和父元素，并保存
                        $dragItem->pid = $pid;
                        $dragItem->sort = $dropItem->sort - 1;
                        $dragItem->save();
                        // 更新其他兄弟元素的排序
                        foreach ($siblings as $sibling) {
                            static::updateSortById($sibling->id, $sibling->sort + 1, $pid);
                        }

                        break;

                    // 在放置项后插入拖动项
                    case 'after':
                        // 如果 pid 为 0，则无需对兄弟节点进行排序更新
                        $siblings = $pid !== 0 ? static::getSiblings($dragItem, $dropItem, $pid) : [];

                        // 更新拖动元素的位置和父元素，并保存
                        $dragItem->pid = $pid;
                        $dragItem->sort = $dropItem->sort + 1;
                        $dragItem->save();

                        // 更新其他兄弟元素的排序
                        foreach ($siblings as $sibling) {
                            static::updateSortById($sibling->id, $sibling->sort + 1, $pid);
                        }

                        break;

                    // 将拖动元素作为放置元素的子元素插入
                    case 'inner':
                        // 更新拖动元素的父元素和排序，并保存
                        $dragItem->pid = $dropItem->id;
                        $dragItem->sort = static::getMaxSortByPid($dragItem->pid) + 1;
                        $dragItem->save();

                        break;
                }
            }

            // 返回排序后的菜单项数据
            return [$dragItem, $dropItem];
        }; 

        return static::mk()->runTransaction($fn);
    }
    
    /**
     * 批量删除菜单项
     * @param array $data 要删除的菜单项 ID 列表
     * @return bool 返回删除成功的状态
     */
    public static function removeMenu(array $data): bool
    {
        $fn = function () use ($data) {
            // 找到所有待删除的节点和它们的所有子节点。
            $toDelete = static::mk()->whereIn('id', $data['ids'])->select()->toArray();

            $toDeleteIds = array_column($toDelete, 'id');
            foreach ($toDelete as $item) {
                $toDeleteIds = array_merge($toDeleteIds, static::getDescendantIds($item['id']));
            }

            // 删除所有待删除的节点和它们的所有子节点。
            static::mk()->whereIn('id', $toDeleteIds)->delete();

            return true;
        };

        return static::mk()->runTransaction($fn);
    }
}